package org.bookie.mtest.framework;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.DOMReader;
import org.xml.sax.SAXException;

import com.meterware.httpunit.WebForm;
import com.meterware.httpunit.WebLink;
import com.meterware.httpunit.WebResponse;

/**
 * Application page
 * 
 * @author zubairov
 */
public class Page {

	protected WebResponse resp;

	private static final Log log = LogFactory.getLog(Page.class);
	
	public static final String PAGE_TITLE_PREFIX = "FreeBookie - ";

	/**
	 * Default constructor
	 * 
	 * @param response
	 * @throws SAXException
	 * @throws IOException
	 */
	public Page(WebResponse response) throws IOException, SAXException {
		this.resp = response;
		log.info("We are now on the " + response.getTitle());
		validate();
	}

	@SuppressWarnings("unchecked")
	public Collection<String> getMainMenuItems() throws SAXException {
		log.debug("Building DOM4J DOM from W3C DOM");
		DOMReader reader = new DOMReader();
		Document document = reader.read(resp.getDOM());
		Node menuNode = document.selectSingleNode("//DIV[@id='menu']");
		assertNotNull(menuNode, "Can't find DIV with id=menu");
		log.debug("Found menu containing DIV");
		List<Node> menuItems = menuNode.selectNodes("./UL/LI/A");
		return CollectionUtils.collect(menuItems, new Transformer() {

			public Object transform(Object input) {
				String stringValue = ((Element) input).getStringValue().trim();
				return stringValue;
			}

		});
	}

	/**
	 * Validates state of the page
	 * 
	 * @throws SAXException
	 * @throws IOException
	 * 
	 */
	public void validate() throws IOException, SAXException {
		checkExceptionDisplayed(resp);
		// checkUnlocalizedStrigs(resp);
		log.info("Page " + resp.getTitle() + " was validated successfully");
	}

	/**
	 * Check that there is no exception on the page
	 * 
	 * @param response
	 * @throws IOException
	 * @throws SAXException
	 */
	private void checkExceptionDisplayed(WebResponse response)
			throws IOException, SAXException {
		assertTrue(response.getText().indexOf("Exception") < 0,
				"Exception occured on " + response.getTitle() + " with text:\n"
						+ response.getText());
	}

	/**
	 * Checks that response contains no unlocalized strings TODO Works EXTREMLY
	 * slow!!! Rework it
	 * 
	 * @param response
	 * @throws IOException
	 * @throws SAXException
	 */
	private void checkUnlocalizedStrigs(WebResponse response)
			throws IOException, SAXException {
		Pattern pat = Pattern.compile(".*\\[([A-Z\\.\\-]+).*", Pattern.DOTALL);
		Matcher matcher = pat.matcher(response.getText());
		if (matcher.find()) {
			String missing = matcher.group(1);
			fail("Non-internationalized entry on the page [" + missing
					+ "] on the " + response.getTitle());
		}
	}

	/**
	 * Clicks on the link with given name and validates response
	 * 
	 * @param itemName
	 * @throws SAXException
	 * @throws IOException
	 */
	public void clickOnLinkWithText(String itemName) throws SAXException,
			IOException {
		WebLink link = resp.getLinkWith(itemName);
		assertNotNull(link, msg("Can't find a link with text " + itemName));
		clickOnLink(link);
	}
	
	/**
	 * Clicks on the link with given id and validates response
	 * 
	 * @param id identifier of the link
	 * @throws SAXException
	 * @throws IOException
	 */
	public void clickOnLinkWithId(String id) throws SAXException,
			IOException {
		WebLink link = resp.getLinkWithID(id);
		assertNotNull(link, msg("Can't find a link with id " + id));
		clickOnLink(link);
	}
	
	/**
	 * Validates response for a link
	 * @param link link to validate response for
	 * @throws IOException
	 * @throws SAXException
	 */
	private void clickOnLink(WebLink link) throws IOException, SAXException{
		log.info("Clicking on the link " + link.getURLString());
		resp = link.click();
		log.info("We are now on the " + resp.getTitle());
		validate();
	}

	/**
	 * Assert text present on the page
	 * 
	 * @param text
	 * @throws IOException
	 */
	public void assertTextPresent(String text) throws IOException {
		assertTrue(resp.getText().indexOf(text) >= 0, msg("Text '" + text
				+ "' can't be found"));
	}

	/**
	 * Assert that link presented on the page
	 * 
	 * @param string
	 * @throws SAXException
	 * @throws IOException
	 */
	public void assertLinkPresent(String linkID) throws IOException,
			SAXException {
		assertNotNull(resp.getLinkWithID(linkID), "Link '" + linkID
				+ "' can't be found on the page:" + resp.getText());
	}
	
	/**
	 * Assert the title of the page
	 * @param title
	 * @throws SAXException
	 */
	public void assertPageTitle(String title) throws SAXException{
		assertEquals(resp.getTitle(), PAGE_TITLE_PREFIX+title);
	}

	/**
	 * Create a message that will be shown during assertions
	 * 
	 * @param message
	 * @return
	 * @throws IOException
	 */
	public String msg(String message) throws IOException {
		StringBuffer result = new StringBuffer(message);
		result.append("\nPage content:\n");
		result.append(resp.getText());
		return result.toString();
	}
	
	/**
	 * Called by form component
	 * 
	 * @param resp
	 * @throws IOException
	 * @throws SAXException
	 */
	public void responseChanged(WebResponse resp) throws IOException, SAXException {
		this.resp = resp;
		validate();
	}

	/**
	 * Asserts form exists and returns it
	 * 
	 * @param string
	 * @return 
	 * @throws SAXException 
	 * @throws IOException 
	 */
	public Form getForm(String formID) throws SAXException, IOException {
		WebForm formWithID = resp.getFormWithID(formID);
		assertNotNull(formWithID, msg("Can't find form with ID=" + formID));
		return new Form(formWithID, this);
	}

	/**
	 * Executes login
	 * @param email
	 * @param password
	 * @throws SAXException 
	 * @throws IOException 
	 */
	public Page login(String email, String password) throws IOException, SAXException {
		assertLinkPresent("registerLink");
		Form form = getForm("loginForm");
		form.setField("email", email);
		form.setField("password", password);
		form.submit("submit");
		return this;
	}

	/**
	 * Navigates to the category admin page
	 * @throws IOException 
	 * @throws SAXException 
	 */
	public CategoryAdminPage getCategoryAdminPage() throws SAXException, IOException {
		clickOnLinkWithText("Admin");
		clickOnLinkWithText("Category admin");
		return new CategoryAdminPage(resp);
	}
	
	/**
	 * Navigates to the results page
	 * @throws IOException 
	 * @throws SAXException 
	 */
	public ResultsPage getResultsPage() throws SAXException, IOException{
		clickOnLinkWithText("Results");
		return new ResultsPage(resp);
	}
	
	/**
	 * Navigates to the user list page
	 * @throws IOException 
	 * @throws SAXException 
	 */
	public UserListPage getUserListPage() throws SAXException, IOException{
		clickOnLinkWithText("Members");
		return new UserListPage(resp);
	}
	
	/**
	 * Navigates to the groups page
	 * @throws IOException 
	 * @throws SAXException 
	 */
	public GroupsPage getGroupsPage() throws SAXException, IOException{
		clickOnLinkWithText("Members");
		clickOnLinkWithText("Groups");
		return new GroupsPage(resp);
	}
	
	/**
	 * Navigates to the group details page
	 * @throws IOException 
	 * @throws SAXException 
	 */
	public GroupDetailsPage getGroupDetailsPage() throws SAXException, IOException{
		clickOnLinkWithText("Members");
		clickOnLinkWithText("Groups");
		clickOnLinkWithText("Simpson family");
		return new GroupDetailsPage(resp);
	}
}
